package club.kynb.mall.application.service;

import club.kynb.mall.application.constant.MallResultCode;
import club.kynb.mall.application.context.MallUserContext;
import club.kynb.mall.application.context.OrderPayContext;
import club.kynb.mall.application.convert.MallCommonConvertor;
import club.kynb.mall.application.flow.pay.BuildPayParamFilter;
import club.kynb.mall.application.flow.pay.PayCompleteNotifyOrderFilter;
import club.kynb.mall.application.flow.pay.PayInvokeFilter;
import club.kynb.mall.application.model.model.command.OrderPayCommand;
import club.kynb.mall.application.model.model.event.OrderPayNotifyEvent;
import club.kynb.mall.application.model.model.query.PayResultQuery;
import club.kynb.mall.application.model.model.vo.OrderPayResultVO;
import club.kynb.mall.order.api.IOrderService;
import club.kynb.mall.order.constant.OrderPayStatusEnum;
import club.kynb.mall.order.model.dto.OrderDTO;
import club.kynb.mall.payment.api.IPayChannelService;
import club.kynb.mall.payment.api.IPayService;
import club.kynb.mall.payment.constant.PayClientTypeEnum;
import club.kynb.mall.payment.constant.PayOrderTypeEnum;
import club.kynb.mall.payment.dto.PayChannelDTO;
import club.kynb.mall.payment.dto.ext.PayQueryDTO;
import club.kynb.mall.payment.dto.ext.PayQueryResultDTO;
import club.kynb.mall.payment.dto.ext.PayResultDTO;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pizza.common.web.exception.Errors;
import org.pizza.event.publish.EventBus;
import org.pizza.pattern.filter.FilterChain;
import org.pizza.pattern.filter.FilterChainFactory;
import org.pizza.util.Checker;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author kynb_club@163.com
 * @date: 2021/7/1
 */
@Slf4j
@Service
@AllArgsConstructor
public class PaymentApplicationService {

    final IPayChannelService payChannelService;
    final IPayService payService;
    final IOrderService orderService;
    private final EventBus eventBus;


    public List<PayChannelDTO> listOrderPayChannel(String orderType){
        PayOrderTypeEnum orderTypeEnum = PayOrderTypeEnum.get(orderType).orElseThrow(()->Errors.BIZ.exception("订单类型错误"));
        PayClientTypeEnum clientTypeEnum = PayClientTypeEnum.get(MallUserContext.getHeader().getDeviceType()).orElseThrow(()->Errors.SYSTEM.exception("系统错误：客户端类型错误"));
        List<PayChannelDTO> payChannelDTOS = payChannelService.listUsable(orderTypeEnum,clientTypeEnum);
        Checker.ifEmptyThen(payChannelDTOS,(s)->{
            log.error("订单 orderType:{} 未获取到可用的支付方式 ",orderType);
            throw Errors.BIZ.exception(MallResultCode.CODE_14001);
        });
        return payChannelDTOS;
    }

    public PayResultDTO create(OrderPayCommand command) {
        final OrderPayContext orderPayContext = new OrderPayContext();
        orderPayContext.setCommand(command);
        final FilterChain<OrderPayContext> filterChain = this.buildOrderPayChain();
        filterChain.doFilter(orderPayContext);
        return orderPayContext.getPayResultDTO();
    }


    public OrderPayResultVO query(PayResultQuery payResultQuery) {
        log.info("payQuery[1] start: orderNo:{}  orderType:{}",payResultQuery.getInnerOrderNo(),payResultQuery.getOrderType());
        String innerOrderNo = payResultQuery.getInnerOrderNo();
        PayOrderTypeEnum orderTypeEnum = PayOrderTypeEnum.get(payResultQuery.getOrderType()).orElseThrow(()->Errors.BIZ.exception("订单类型错误"));
        OrderPayResultVO orderPayResult = loadOrderPayStatus(innerOrderNo,orderTypeEnum);
        log.info("payQuery[2] loadResult:{}", JSONUtil.toJsonStr(orderPayResult));
        //支付中
        if(OrderPayStatusEnum.PAYING.equals(orderPayResult.getStatus())){
            //2. 获取支付交易状态
            PayQueryResultDTO resultDTO = payService.query(new PayQueryDTO().setOrderNo(innerOrderNo).setOrderType(orderTypeEnum));
            OrderPayStatusEnum payStatusEnum = MallCommonConvertor.convertPayStatusFromTradeStatus(resultDTO.getStatus());
            //2.1 还在中间状态直接返回
            if(OrderPayStatusEnum.PAYING.equals(payStatusEnum)){
                return new OrderPayResultVO().setStatus(OrderPayStatusEnum.PAYING);
            }
            orderPayResult.setStatus(payStatusEnum);
        }
        log.info("payQuery[3] loadResult:{}", JSONUtil.toJsonStr(orderPayResult));
        //3. 发事件
        eventBus.publishEvent(OrderPayNotifyEvent.builder()
                .innerOrderNo(innerOrderNo)
                .orderTypeEnum(orderTypeEnum)
                .statusEnum(orderPayResult.getStatus())
                .payAmount(orderPayResult.getPayAmount())
                .payTime(orderPayResult.getPayFinishTime())
                .build()
        );
        return orderPayResult;
    }

    private OrderPayResultVO loadOrderPayStatus(String innerOrderNo, PayOrderTypeEnum orderTypeEnum){
        OrderPayStatusEnum orderPayStatusEnum;
        switch (orderTypeEnum){
            case NORMAL:
                OrderDTO order = orderService.getOrderBy(innerOrderNo).orElseThrow(()->Errors.BIZ.exception("订单编号有误，没有找到对应的订单"));
                orderPayStatusEnum = OrderPayStatusEnum.get(order.getPayStatus()).orElse(OrderPayStatusEnum.NO_PAY);
                return new OrderPayResultVO().setStatus(orderPayStatusEnum).setChannel(order.getPayChannel())
                        .setPayAmount(order.getPayAmount()).setPayFinishTime(order.getPayFinishTime());
            default:
                throw Errors.BIZ.exception("订单类型错误");
        }
    }

    private FilterChain<OrderPayContext> buildOrderPayChain() {
        return FilterChainFactory.buildFilterChain(
                BuildPayParamFilter.class
                , PayInvokeFilter.class
                , PayCompleteNotifyOrderFilter.class
        );
    }
}
